package com.kmxd.ams.app.archive;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveFieldSvc;
import com.kmxd.ams.client.archive.IArchiveRelationSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.ArchiveRelationAddDTO;
import com.kmxd.ams.client.archive.dto.ArchiveRelationArchQueryDTO;
import com.kmxd.ams.client.archive.dto.ArchiveRelationEditDTO;
import com.kmxd.ams.client.archive.dto.ArchiveRelationQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveFieldVO;
import com.kmxd.ams.client.archive.vo.ArchiveRelationFilterListVO;
import com.kmxd.ams.client.archive.vo.ArchiveRelationListVO;
import com.kmxd.ams.client.archive.vo.ArchiveRelationVO;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IVolumeSvc;
import com.kmxd.ams.client.event.log.DocumentLogEvent;
import com.kmxd.ams.client.event.log.VolumeLogEvent;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.enums.*;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.exception.ParamsValidateException;
import com.kmxd.ams.core.util.ConvertUtil;
import com.kmxd.ams.core.util.DynamicTableNameUtil;
import com.kmxd.ams.core.util.FieldUtil;
import com.kmxd.ams.core.util.IdUtil;
import com.kmxd.ams.infra.archive.entity.ArchiveField;
import com.kmxd.ams.infra.archive.entity.ArchiveRelation;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.archive.mapper.ArchiveRelationMapper;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Volume;
import com.kmxd.ams.infra.tmpl.dto.ArchTmplBO;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 门类关联关系 服务实现类
 *
 * @author nickbi
 * @since 2022-07-20
 */
@Service
@AllArgsConstructor
public class ArchiveRelationSvcImpl extends ServiceImpl<ArchiveRelationMapper, ArchiveRelation>
    implements IArchiveRelationSvc {

  private ArchiveRelationMapper archiveRelationMapper;

  private IArchiveTypeSvc archiveTypeSvc;
  private IDocumentSvc documentSvc;
  private IArchiveFieldSvc archiveFieldSvc;

  private IVolumeSvc volumeSvc;

  private AmsEventPublisher publisher;

  @Override
  public Page<ArchiveRelationListVO> selectPage(Page reqPage, ArchiveRelationQueryDTO req) {

    LambdaQueryWrapper<ArchiveRelation> queryWrapper =
        QueryGen.init(new QueryWrapper<ArchiveRelation>(), req).lambda();
    Page<ArchiveRelation> page = archiveRelationMapper.selectPage(reqPage, queryWrapper);
    Page<ArchiveRelationListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    List<ArchiveRelationListVO> list =
        BeanUtil.copyToList(page.getRecords(), ArchiveRelationListVO.class);
    list.forEach(
        item -> {
          buildListVO(item);
          if (ArchRelationEnum.META.getCode().equals(item.getRelationType())) {
            Long num = getMatchNum(item);
            item.setStatus(
                num == 0
                    ? ArchRelationStatusEnum.NOT_MATCH.getCode()
                    : ArchRelationStatusEnum.MATCH.getCode());
            item.setArchNum(num);
          }
        });
    voPage.setRecords(list);
    return voPage;
  }

  private void buildListVO(ArchiveRelationListVO vo) {
    ArchiveType archiveType = archiveTypeSvc.getById(vo.getReferArchTypeId());
    vo.setArchiveTypeInfo(
        StrUtil.format(
            "{}-{}", archiveType.getName(), ArchTmplBO.getTableDescription(vo.getReferTableId())));
    if (ArchRelationEnum.DATA.getCode().equals(vo.getRelationType())) {
      vo.setRelationInfo("直接选择档案关联");
      copyArchInfo(vo);

    } else if (ArchRelationEnum.META.getCode().equals(vo.getRelationType())) {
      ArchiveField filed = archiveFieldSvc.getById(vo.getRelationFieldId());
      vo.setRelationInfo(StrUtil.format("{}={}", filed.getDbFieldName(), vo.getRelationIdentity()));
    }
  }

  private void copyArchInfo(ArchiveRelationListVO vo) {
    if (vo.getReferTableId().contains(ArchTmplBO.DOCUMENT)) {
      Document qry = new Document();
      qry.setTableId(vo.getReferTableId());
      qry.setId(vo.getReferDataId());
      Document referDoc = documentSvc.view(qry);
      vo.setTitle(referDoc.getTitle());
      vo.setStatus(referDoc.getStatus());
      vo.setArchCode(referDoc.getArchCode());
    }
    if (vo.getReferTableId().contains(ArchTmplBO.VOLUME)) {
      Volume qry = new Volume();
      qry.setTableId(vo.getReferTableId());
      qry.setId(vo.getReferDataId());
      Volume referVol = volumeSvc.view(qry);
      vo.setTitle(referVol.getTitle());
      vo.setStatus(referVol.getStatus());
      vo.setArchCode(referVol.getArchCode());
    }
  }

  @Override
  public List<ArchiveRelationListVO> selectList(ArchiveRelationQueryDTO req) {
    LambdaQueryWrapper<ArchiveRelation> queryWrapper =
        QueryGen.init(new QueryWrapper<ArchiveRelation>(), req).lambda();
    List<ArchiveRelation> list = archiveRelationMapper.selectList(queryWrapper);
    return BeanUtil.copyToList(list, ArchiveRelationListVO.class);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(ArchiveRelationAddDTO req) {
    if (ArchRelationEnum.DATA.getCode().equals(req.getRelationType())) {
      if (StrUtil.isAllBlank(req.getDocIds(), req.getVolIds())) {
        throw ParamsValidateException.instance(
            FieldUtil.getFieldName(ArchiveRelationAddDTO::getDocIds), ParamsValidateType.NOT_NULL);
      }

      Set<Long> ids = IdUtil.split(req.getDocIds());
      if (ObjectUtil.isNotEmpty(req.getVolIds())) {
        ids = IdUtil.split(req.getVolIds());
      }
      if (req.getTableId().equals(req.getReferTableId())) {
        ids.remove(req.getDataId());
      }
      List<ArchiveRelation> relationList =
          ids.stream()
              .map(
                  id -> {
                    ArchiveRelation entity = BeanUtil.copyProperties(req, ArchiveRelation.class);
                    ArchiveRelationListVO vo =
                        ConvertUtil.toBean(entity, ArchiveRelationListVO.class);
                    vo.setReferDataId(id);
                    copyArchInfo(vo);
                    BeanUtil.copyProperties(vo, entity);
                    entity.setSourceIdentity(ArchRelationEnum.DATA.getCode());
                    entity.setSourceInfo(ArchRelationEnum.DATA.getDesc());
                    return entity;
                  })
              .collect(Collectors.toList());
      saveBatch(relationList);
      relationList.forEach(
          item ->
              saveArchLog(req, item, String.format("关联档案：直接选择档案，关联标识：%s", item.getReferDataId())));

    } else if (ArchRelationEnum.META.getCode().equals(req.getRelationType())) {

      ArchiveRelation entity = BeanUtil.copyProperties(req, ArchiveRelation.class);
      ArchiveFieldVO field = archiveFieldSvc.view(req.getRelationFieldId());
      entity.setReferArchTypeId(field.getArchTypeId());
      entity.setReferTableId(field.getTableId());
      entity.setSourceIdentity(ArchRelationEnum.META.getCode());
      entity.setSourceInfo(ArchRelationEnum.META.getDesc());
      Long matchNum = getMatchNum(ConvertUtil.toBean(entity, ArchiveRelationListVO.class));
      Integer status =
          matchNum == 0
              ? ArchRelationStatusEnum.NOT_MATCH.getCode()
              : ArchRelationStatusEnum.MATCH.getCode();
      entity.setStatus(status);
      entity.setArchNum(matchNum);
      archiveRelationMapper.insert(entity);
      saveArchLog(
          req,
          entity,
          String.format(
              "关联档案：%s=%s，关联标识：%s",
              field.getDbFieldName(), entity.getRelationIdentity(), entity.getRelationIdentity()));
    }
  }

  private void saveArchLog(ArchiveRelationAddDTO req, ArchiveRelation item, String content) {
    if (req.getTableId().contains(ArchTmplBO.DOCUMENT)) {
      Document qry = new Document();
      qry.setArchTypeId(req.getArchTypeId());
      qry.setTableId(req.getTableId());
      qry.setDocIds(item.getDataId().toString());
      publisher.publishEvent(new DocumentLogEvent(qry, ArchOpEnum.FILE_ASSOCIATION, content));
    } else {
      Volume qry = new Volume();
      qry.setArchTypeId(req.getArchTypeId());
      qry.setTableId(req.getTableId());
      qry.setVolIds(item.getDataId().toString());
      publisher.publishEvent(new VolumeLogEvent(qry, ArchOpEnum.FILE_ASSOCIATION, content));
    }
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(ArchiveRelationEditDTO req) {
    ArchiveRelation entity = BeanUtil.copyProperties(req, ArchiveRelation.class);
    archiveRelationMapper.updateById(entity);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(String ids) {
    Set<Long> idList = IdUtil.split(ids);
    this.baseMapper.deleteBatchIds(idList);
  }

  @Override
  public ArchiveRelationVO view(Long id) {
    ArchiveRelation entity = archiveRelationMapper.selectById(id);
    return BeanUtil.copyProperties(entity, ArchiveRelationVO.class);
  }

  @Override
  public List<ArchiveRelationFilterListVO<Long>> filterList(ArchiveRelationQueryDTO req) {

    List<ArchiveRelationFilterListVO<Long>> resList = Lists.newArrayList();
    ArchiveRelationFilterListVO<Long> all = new ArchiveRelationFilterListVO<>();
    all.setLabel("全部门类");
    all.setValue(null);
    resList.add(all);

    List<ArchiveRelation> list =
        list(
            Wrappers.<ArchiveRelation>query()
                .select("distinct refer_arch_type_id")
                .lambda()
                .eq(ArchiveRelation::getArchTypeId, req.getArchTypeId())
                .eq(ArchiveRelation::getTableId, req.getTableId())
                .eq(ArchiveRelation::getDataId, req.getDataId())
                .eq(ArchiveRelation::getRelationType, req.getRelationType()));
    if (ObjectUtil.isEmpty(list)) {
      return resList;
    }
    List<Long> archTypeIds =
        list.stream()
            .map(ArchiveRelation::getReferArchTypeId)
            .distinct()
            .collect(Collectors.toList());
    List<ArchiveType> archiveTypes = archiveTypeSvc.listByIds(archTypeIds);
    List<ArchiveRelationFilterListVO<Long>> res =
        archiveTypes.stream()
            .map(
                arch -> {
                  ArchiveRelationFilterListVO<Long> vo = new ArchiveRelationFilterListVO<>();
                  vo.setValue(arch.getId());
                  vo.setLabel(arch.getName());
                  return vo;
                })
            .collect(Collectors.toList());

    if (ObjectUtil.isNotEmpty(res)) {
      resList.addAll(res);
    }

    return resList;
  }

  @Override
  public List<ArchiveRelationListVO> relationArchList(Long id) {
    if (Objects.isNull(id)) {
      throw ParamsValidateException.instance(
          FieldUtil.getFieldName(ArchiveRelation::getId), ParamsValidateType.NOT_NULL);
    }
    ArchiveRelation relation = getById(id);
    if (ArchRelationEnum.DATA.getCode().equals(relation.getRelationType())) {
      List<ArchiveRelationListVO> res = Lists.newArrayList();
      res.add(ConvertUtil.toBean(relation, ArchiveRelationListVO.class));
      return res;
    }
    return getRelationData(relation);
  }

  private List<ArchiveRelationListVO> getRelationData(ArchiveRelation relation) {
    String tableId = relation.getReferTableId();
    ArchiveField field = archiveFieldSvc.getById(relation.getRelationFieldId());
    if (tableId.contains(ArchTmplBO.DOCUMENT)) {
      DynamicTableNameUtil.setTableName(tableId);
      List<Document> documentList =
          documentSvc.list(
              Wrappers.<Document>query().eq(field.getDbFieldKey(), relation.getRelationIdentity()));
      return buildRelationList(relation, documentList);
    }
    if (tableId.contains(ArchTmplBO.VOLUME)) {
      DynamicTableNameUtil.setTableName(tableId);
      List<Volume> volumeList =
          volumeSvc.list(
              Wrappers.<Volume>query().eq(field.getDbFieldKey(), relation.getRelationIdentity()));
      return buildRelationList(relation, volumeList);
    }
    return Lists.newArrayList();
  }

  private List<ArchiveRelationListVO> buildRelationList(
      ArchiveRelation relation, List<? extends Object> list) {
    return list.stream()
        .map(
            item -> {
              ArchiveRelationListVO vo = ConvertUtil.toBean(relation, ArchiveRelationListVO.class);
              BeanUtil.copyProperties(
                  item,
                  vo,
                  CopyOptions.create()
                      .setFieldMapping(
                          ImmutableMap.of(
                              "id",
                              "dataId",
                              "title",
                              "title",
                              "archCode",
                              "archCode",
                              "status",
                              "status")));
              buildListVO(vo);
              return vo;
            })
        .filter(
            item ->
                !(item.getTableId().equals(item.getReferTableId())
                    && item.getDataId().equals(item.getReferDataId())))
        .collect(Collectors.toList());
  }

  private Long getMatchNum(ArchiveRelationListVO vo) {
    ArchiveField field = archiveFieldSvc.getById(vo.getRelationFieldId());
    String tableId = vo.getReferTableId();
    if (tableId.contains(ArchTmplBO.DOCUMENT)) {
      return buildStatus(documentSvc, tableId, field.getDbFieldKey(), vo.getRelationIdentity());
    }
    if (tableId.contains(ArchTmplBO.VOLUME)) {
      return buildStatus(volumeSvc, tableId, field.getDbFieldKey(), vo.getRelationIdentity());
    }
    return 0L;
  }

  /**
   * 设置按元数据关联的数据匹配信息， 10未匹配，20匹配到
   *
   * @param service
   * @param tableId
   * @param relationField
   * @param relationIdentity
   * @return
   */
  private Long buildStatus(
      IService service, String tableId, String relationField, String relationIdentity) {
    QueryWrapper<Object> wrapper = Wrappers.query();
    wrapper.eq(relationField, relationIdentity);
    DynamicTableNameUtil.setTableName(tableId);
    wrapper.select(
        String.format("%s(%s) as %s", SummaryTypeEnum.COUNT.getFunc(), ArchConst.DEF_ID, "count"));
    Map map = service.getMap(wrapper);
    Object count = map.get("count");
    return Objects.isNull(count) ? 0 : (Long) count;
  }

  @Override
  public List<ArchiveRelationListVO> archList(ArchiveRelationArchQueryDTO req) {
    List<ArchiveRelation> list =
        list(
            Wrappers.<ArchiveRelation>lambdaQuery()
                .eq(ArchiveRelation::getArchTypeId, req.getArchTypeId())
                .eq(ArchiveRelation::getTableId, req.getTableId())
                .eq(ArchiveRelation::getDataId, req.getDataId()));
    if (ObjectUtil.isEmpty(list)) {
      return Lists.newArrayList();
    }
    return list.stream()
        .map(
            item -> {
              if (item.getRelationType().equals(ArchRelationEnum.DATA.getCode())) {
                ArchiveRelationListVO vo = ConvertUtil.toBean(item, ArchiveRelationListVO.class);
                buildListVO(vo);
                return Lists.newArrayList(vo);
              }
              return relationArchList(item.getId());
            })
        .flatMap(List::stream)
        .collect(Collectors.toList());
  }
}
